#include"2048.h"
/* 初始化游戏 */
void InitGame()
{
#ifdef _WIN32
	system("cls");
	char CurDir[MAX_PATH];
	_getcwd(CurDir, MAX_PATH);
	sprintf(historyBest, "%sbestScore.dat", CurDir);

#else
	/* 获取游戏存档路径，Linux下放在当前用户主目录下 */
	char CurDir[MAX_PATH];
	getcwd(CurDir, MAX_PATH);
	sprintf(historyBest, "%s/2048.txt", CurDir);

	tcgetattr(0, &old_config);              /* 获取终端属性 */
	struct termios new_config = old_config; /* 创建新的终端属性 */
	new_config.c_lflag &= ~ICANON;          /* 设置非正规模式 */
	new_config.c_lflag &= ~ECHO;            /* 关闭输入回显 */
	new_config.c_cc[VMIN] = 1;              /* 设置非正规模式下的最小字符数 */
	new_config.c_cc[VTIME] = 0;             /* 设置非正规模式下的读延时 */
	tcsetattr(0, TCSANOW, &new_config);     /* 设置新的终端属性 */
	printf("\033[?25l");

	signal(SIGINT, EndGame);
#endif

	/* 读取游戏最高分数 */
	FILE *fp = fopen(historyBest, "r");
	if (fp)
	{
		fread(&highest_score, sizeof(highest_score), 1, fp);
		fclose(fp);

	}
	else
	{
		highest_score = 0;
		fp = fopen(historyBest, "w");
		if (fp)
		{
			fwrite(&highest_score, sizeof(highest_score), 1, fp);
			fclose(fp);
		}
	}
}

void InitBoard()
{
	score = 0;
	if_random = true;
	game_over = false;
	if_exit = false;
	if_restart = false;
	/*表格全部置为0,防止不能生成随机位置 */
	for (int i = 0; i < ROW; ++i)
	{
		for (int j = 0; j < COL; ++j)
			board[i][j] = 0;
	}

	/* 游戏开始先随机生成一个2，其他均为0 */
	srand((unsigned)time(NULL));//生成种子
	int row = rand() % ROW;
	int col = rand() % COL;
	board[row][col] = 2;
	/* 再生成一个随机的2或4，概率之比1:1 */
	GenerateRandPosition();

	/* 刷新界面 */
	RefreshBoard();
}

// 刷新界面 函数定义
void RefreshBoard()
{
	ClearScreen();

	printf("\n\n\n\n");
	printf("                             GAME_NAME: 2048 \n\n");
	printf("                      SCORE: %5d     BEST: %5d\n", score, highest_score);
	printf("               --------------------------------------------------");

	/* 绘制方格和数字 */
	printf("\n\n                             ┌──");
	for (int i = 0; i < COL - 1; ++i)
		printf("──┬──");
	printf("──┐\n");


	for (int i = 0; i < ROW; ++i)
	{
		printf("                             │");
		for (int j = 0; j < COL; ++j)
		{
			if (board[i][j] != 0)
			{
				if (board[i][j] < 10)
				{
					printf("  %d │", board[i][j]);
				}
				else if (board[i][j] < 100)
				{
					printf(" %d │", board[i][j]);
				}
				else if (board[i][j] < 1000)
				{
					printf(" %d│", board[i][j]);
				}
				else if (board[i][j] < 10000)
				{
					printf("%4d│", board[i][j]);
				}
				else
				{
					//计算超出10000应该是2的多少次方如2^10形式 
					int n = board[i][j];
					for (int k = 1; k < 20; ++k)
					{
						n = n >> 1;
						if (n == 1)
						{
							printf("2^%2d│", k);
							break;
						}
					}
				}
			}
			else
				printf("    │");
		}

		if (i < COL - 1)
		{
			printf("\n                             ├──");
			for (int i = 0; i < COL - 1; ++i)
				printf("──┼──");
			printf("──┤\n");
		}
		else
		{
			printf("\n                             └──");
			for (int i = 0; i < COL - 1; ++i)
				printf("──┴──");
			printf("──┘\n");
		}
	}
	printf("\n");
	printf("               --------------------------------------------------\n");
	printf("                  [w]:UP [s]:Down [a]:Left [d]:Right [r]:Restart [q]:Exit ");

	if (GetEmptyCount() == 0)
	{
		CheckGameOver();

		/* 判断是否输掉游戏 */
		if (game_over == true)
		{
			//\b表示退格，与backspace不同的是不删除元素，下次输入从倒数第\b个元素开始覆盖
			printf("\r                      GAME OVER! TRY AGAIN? [y/n]:                         \b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");

#ifdef _WIN32
			CONSOLE_CURSOR_INFO info = { 1, 1 };
			SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
#else
			printf("\033[?25h"); /* linux下的显示输入光标 */
#endif
		}
	}

	/* 判断是否准备退出游戏 */
	if (if_exit == true)
	{
		printf("\r                   DO YOU REALLY WANT TO QUIT THE GAME? [Y/N]:           \b\b\b\b\b\b\b\b\b\b");
#ifdef _WIN32
		CONSOLE_CURSOR_INFO info = { 1, 1 };
		SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
#else
		printf("\033[?25h"); /* linux下的显示输入光标 */
#endif
	}

	/* 判断是否重开游戏 */
	if (if_restart == true)
	{
		printf("\r                   DO YOU REALLY WANT TO RESTART THE GAME? [Y/N]:           \b\b\b\b\b\b\b\b\b\b");
#ifdef _WIN32
		CONSOLE_CURSOR_INFO info = { 1, 1 };
		SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
#else
		printf("\033[?25h"); /* linux下的显示输入光标 */
#endif
	}
	fflush(0); /* 刷新输出缓冲区 */
}

/* 开始游戏 函数定义 */
void PlayGame()
{
	while (1)
	{
		int operate = ReadKeyboard(); /* 接收标准输入流字符命令 */

									  /* 判断是否准备退出游戏 */
		if (if_exit == true)
		{
			if (operate == 'y' || operate == 'Y')
			{
				/* 退出游戏，清屏后退出 */
				ClearScreen();
				return;
			}
			else if (operate == 'n' || operate == 'N')
			{
				/* 取消退出 */
				if_exit = false;
				RefreshBoard();
				continue;
			}
			else
			{ //无效输入，继续进行循环，直到用户输入有效选择
				continue;
			}
		}

		/*是否是重新开始游戏*/
		if (if_restart == true)
		{
			if (operate == 'y' || operate == 'Y')
			{
				/* 重新游戏 */
				RefreshBoard();
				InitBoard();
			}
			else if (operate == 'n' || operate == 'N')
			{
				/* 取消重新开始 */
				if_restart = false;
				RefreshBoard();
				continue;
			}
			else
			{ //无效输入，继续进行循环，直到用户输入有效选择
				continue;
			}
		}

		/* 游戏已结束，判断是否需要继续*/
		if (game_over == true)
		{
			if (operate == 'y' || operate == 'Y')
			{
				InitGame();
				continue;
			}
			else if (operate == 'n' || operate == 'N')
			{
				ClearScreen();
				return;
			}
			else
			{
				continue;
			}
		}

		if_random = false; /* 先设定不默认需要生成随机数，需要时再设定为1 */

#ifdef _WIN32
						   /* 命令解析，除了上下左右箭头w，s，a，d字符代表上下左右右命令，q代表退出 */
		switch (operate)
		{
			//具体keycode可以用_getch函数输入打印查看
		case 'w':
		case 72:UpMove(); break;
		case 's':
		case 80:DownMove(); break;
		case 'a':
		case 75:LeftMove(); break;
		case 'd':
		case 77:RightMove(); break;
		case 'r':
			if_restart = true; break;
		case 'q':
		case 27:if_exit = true; break;
		default:continue;
		}

#else
		switch (operate)
		{
		case 'a':
		case KEY_CODE_LEFT:LeftMove();
			break;
		case 's':
		case KEY_CODE_DOWN:DownMove();
			break;
		case 'w':
		case KEY_CODE_UP:UpMove();
			break;
		case 'd':
		case KEY_CODE_RIGHT:RightMove();
			break;
		case 'r':
			if_restart = true; break;
		case KEY_CODE_QUIT:if_exit = true;
			break;
		default:continue;
		}
#endif

		/* 需要时更新最高分 */
		if (score > highest_score)
		{
			highest_score = score;
			FILE *fp = fopen(historyBest, "w");
			if (fp)
			{
				fwrite(&highest_score, sizeof(highest_score), 1, fp);
				fclose(fp);
			}
		}

		/* 默认为需要生成随机数时也同时需要刷新显示，反之亦然 */
		if (if_random == true)
		{
			GenerateRandPosition();
			RefreshBoard();
		}
		else if (if_exit == true)
		{
			RefreshBoard();
		}
		if (if_restart == true)
		{
			RefreshBoard();
		}
	}
}

/* 读取键盘操作符 */
int ReadKeyboard()
{
#ifdef _WIN32
	return _getch();//不回显函数，输入一个字符无需回车直接读入
#else
	int key_code;
	if (read(0, &key_code, 1) < 0)
	{
		return -1;
	}
	return key_code;
#endif
}

//左移
void LeftMove()
{
	int i;
	for (i = 0; i < ROW; ++i)
	{
		// 变量j为列标，变量k为待比较项的列标，循环每行进入判断
		for (int j = 1, k = 0; j < COL; ++j)
		{
			if (board[i][j] > 0) // 找出k后面第一个不为空的列项
			{
				if (board[i][k] == 0) /*k列为空，后面非空直接覆盖*/
				{
					// 情况2：k项为空，则把j项赋值给k项
					/*相当于j方块移动到k方块*/
					board[i][k] = board[i][j];
					board[i][j] = 0;
					if_random = true;
				}
				else if (board[i][k] == board[i][j]) /*k列非空且和后面非空项相等则合并*/
				{
					board[i][k++] *= 2;
					score += board[i][k];
					board[i][j] = 0;
					if_random = true;
				}
				else /*否则，k列后移，让后面非空项覆盖k列*/
				{
					++k;
					board[i][k] = board[i][j];
					if (j != k)
					{
						board[i][j] = 0;
						if_random = true;
					}
				}
			}
		}
	}
}

//右移
void RightMove()
{
	// 仿照左移操作，区别仅仅是j和k都反向遍历 
	for (int i = 0; i < ROW; ++i)
	{
		for (int j = COL - 2, k = COL - 1; j >= 0; --j)
		{
			if (board[i][j] > 0) {
				if (board[i][k] == board[i][j])
				{
					score += board[i][k--] *= 2;
					board[i][j] = 0;
					if_random = true;
				}
				else if (board[i][k] == 0)
				{
					board[i][k] = board[i][j];
					board[i][j] = 0;
					if_random = true;
				}
				else
				{
					board[i][--k] = board[i][j];
					if (j != k)
					{
						board[i][j] = 0;
						if_random = true;
					}
				}
			}
		}
	}
}

//上移
void UpMove()
{
	for (int i = 0; i < COL; ++i)
	{
		for (int j = 1, k = 0; j < ROW; ++j)
		{
			if (board[j][i] > 0)
			{
				if (board[k][i] == board[j][i])
				{
					score += board[k++][i] *= 2;
					board[j][i] = 0;
					if_random = true;
				}
				else if (board[k][i] == 0)
				{
					board[k][i] = board[j][i];
					board[j][i] = 0;
					if_random = true;
				}
				else
				{
					board[++k][i] = board[j][i];
					if (j != k)
					{
						board[j][i] = 0;
						if_random = true;
					}
				}
			}
		}
	}
}

//下移
void DownMove()
{
	for (int i = 0; i < COL; ++i)
	{
		for (int j = ROW - 2, k = ROW - 1; j >= 0; --j)
		{
			if (board[j][i] > 0)
			{
				if (board[k][i] == board[j][i])
				{
					score += board[k--][i] *= 2;
					board[j][i] = 0;
					if_random = true;
				}
				else if (board[k][i] == 0)
				{
					board[k][i] = board[j][i];
					board[j][i] = 0;
					if_random = true;
				}
				else
				{
					board[--k][i] = board[j][i];
					if (j != k)
					{
						board[j][i] = 0;
						if_random = true;
					}
				}
			}
		}
	}
}

//清屏
void ClearScreen()
{
#ifdef _WIN32
	/* 重设光标输出位置清屏*/
	COORD pos = { 0, 0 };  //光标坐标
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
	CONSOLE_CURSOR_INFO info = { 1, 0 }; //
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
#else
	printf("\033c");     /* linux下的清屏命令 */
	printf("\033[?25l"); /* linux下的隐藏输入光标 */
#endif
}


/* 结束游戏 */
void EndGame(int signal)
{
#ifdef _WIN32
	system("cls");
	CONSOLE_CURSOR_INFO info = { 1, 1 };
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
#else
	if (signal == SIGINT)
	{
		printf("\n");
	}
	if (tcsetattr(0, TCSANOW, &old_config) != 0) /* 还原回旧的终端属性 */
		perror("tcsetattr");
	printf("\033[?25h"); /*恢复显示光标*/
#endif
	exit(0);
}



/* 生成随机数 函数定义 */
void GenerateRandPosition()
{
	srand((unsigned int)time(0));
	int n = rand() % GetEmptyCount(); /* 在第n个空位置生成随机数 */
	for (int i = 0; i < ROW; ++i)
	{
		for (int j = 0; j < COL; ++j)
		{
			if (board[i][j] == 0 && n-- == 0)
			{
				board[i][j] = ((rand() % 2 == 0) ? 2 : 4); /* 生成字2或4，生成概率为1:1 */
				return;
			}
		}
	}
}

/* 获取空位置数量 */
int GetEmptyCount()
{
	int n = 0;
	for (int i = 0; i < ROW; ++i)
	{
		for (int j = 0; j < COL; ++j)
		{
			if (board[i][j] == 0)
				++n;
		}
	}
	return n;
}

/* 检测游戏是否结束，如果上下或者左右都不能结合则游戏结束，0和0也是种结合，避免了游戏开始会直接结束*/
void CheckGameOver()
{
	for (int i = 0; i < ROW; ++i)
	{
		for (int j = 0; j < COL - 1; ++j)
		{
			//横向和纵向比较挨着的两个元素是否相等，若有相等则游戏不结束 
			//一方面保证了访问有效性，不会越界；一方面只需遍历一半的表格
			if (board[i][j] == board[i][j + 1] || board[j][i] == board[j + 1][i])
			{
				game_over = false;
				return;
			}
		}
	}
	game_over = true;
}